/** Written by Thomas Tharp, September 2011
 * For RIT 4003-714-01, Professor James Heliotis
 * Hash Table Lab
**/

using System;
using System.Collections.Generic;
using RIT_CS;

namespace Tharp
{
    //public class Bucket<Key, Value>
    //{
    //    private List<Tuple<Key, Value>> bucketList = new List<Tuple<Key, Value>>();

    //    public static readonly Func<Tuple<Key, Value>, Func<Tuple<Key, Value>, Boolean>> COMPARE_FIRST = (entry1) => (entry2) => entry1.Item1.Equals(entry2.Item1);
    //    public static readonly Func<Tuple<Key, Value>, Func<Tuple<Key, Value>, Boolean>> COMPARE_SECOND = (entry1) => (entry2) => entry1.Item2.Equals(entry2.Item2);
    //    public static readonly Func<Tuple<Key, Value>, Func<Tuple<Key, Value>, Boolean>> COMPARE_BOTH = (entry1) => (entry2) => entry1.Item1.Equals(entry2.Item1) && entry1.Item2.Equals(entry2.Item2);

    //    /// <summary>
    //    /// Construct a new bucket with no data.
    //    /// </summary>
    //    public Bucket() { }

    //    /// <summary>
    //    /// Construct a new Bucket with an initial entry.
    //    /// </summary>
    //    /// <param name="entry">A Tuple with the same type parameters as the Bucket</param>
    //    public Bucket(Tuple<Key, Value> entry)
    //    {
    //        bucketList.Add(entry);
    //    }//end Bucket

    //    /// <summary>
    //    /// Returns the number of entries in the bucket.
    //    /// </summary>
    //    public int Size()
    //    {
    //        return bucketList.Count;
    //    }//end Size

    //    /// <summary>
    //    /// Add a new entry to the bucket. If an entry with the same Item1 already exists,
    //    /// it will be replaced without error.
    //    /// </summary>
    //    /// <param name="entryToAdd">A Tuple entry to be put in the bucket</param>
    //    public Boolean Add(Tuple<Key, Value> entryToAdd)
    //    {
    //        return Add(entryToAdd, COMPARE_FIRST);
    //    }//end Add(tuple)

    //    /// <summary>
    //    /// Add a new entry to the bucket. Uses a Func to specify what constitutes a duplicate entry.
    //    /// If a duplicate entry already exists, it will be replaced without error.
    //    /// </summary>
    //    /// <param name="entryToAdd">A Tuple entry to be put in the bucket</param>
    //    /// <param name="overwriteCondition">A Func that takes a tuple, and returns a comparer that takes another tuple and returns true if the two are considered duplicates</param>
    //    public Boolean Add(Tuple<Key, Value> entryToAdd, Func<Tuple<Key, Value>, Func<Tuple<Key, Value>, Boolean>> overwriteCondition)
    //    {
    //        //first check if an entry with the same key already exists

    //        int duplicateKeyIndex = bucketList.FindIndex(new Predicate<Tuple<Key, Value>>(overwriteCondition(entryToAdd)));

    //        if (duplicateKeyIndex == -1)
    //        {
    //            //if there's no duplicate keys, simply add the entry, and return false
    //            bucketList.Add(entryToAdd);
    //            return false;
    //        }//end if
    //        else
    //        {
    //            //if there is a duplicate, replace the old entry with the new one, and return true
    //            bucketList[duplicateKeyIndex] = entryToAdd;
    //            return true;
    //        }//end else

    //    }//end Add(tuple,func)

    //    /// <summary>
    //    /// Finds and returns an entry in the bucket based on the equality condition specified.
    //    /// </summary>
    //    /// <param name="baseTuple">The tuple to be found</param>
    //    /// <param name="equalityCondition">A Func that takes a tuple to compare to, and returns a (func) comparer that takes another tuple and returns true if they match</param>
    //    /// <returns>Either a tuple or a null reference if no match was found</returns>
    //    public Tuple<Key, Value> FindEntry(Tuple<Key, Value> baseTuple, Func<Tuple<Key, Value>, Func<Tuple<Key, Value>, Boolean>> equalityCondition)
    //    {
    //        int index = bucketList.FindIndex(new Predicate<Tuple<Key, Value>>(equalityCondition(baseTuple)));

    //        if (index == -1)
    //        {
    //            return null;
    //        }//end if
    //        else
    //        {
    //            return bucketList[index];
    //        }//end else
    //    }//end FindEntry

    //    /// <summary>
    //    /// Finds and returns an entry whose Item1 matches the passed key
    //    /// </summary>
    //    /// <param name="k">The key to compare with Item1</param>
    //    /// <returns>Either a tuple or a null reference if no match was found</returns>
    //    public Tuple<Key, Value> GetEntryByKey(Key k)
    //    {
    //        return FindEntry(new Tuple<Key, Value>(k, default(Value)), COMPARE_FIRST);
    //    }//end GetEntryByKey

    //    /// <summary>
    //    /// Returns a list of tuples comprising all the entries in the bucket
    //    /// </summary>
    //    /// <returns>The list of all entries in the bucket</returns>
    //    public List<Tuple<Key, Value>> Empty()
    //    {
    //        return bucketList;
    //    }//end Empty

    //    /// <summary>
    //    /// Overrides the Object.ToString() method.
    //    /// </summary>
    //    /// <returns>A string represenging the list of tuples in the bucket.  Always returns a non-empty string.</returns>
    //    public override String ToString()
    //    {
    //        string result = string.Empty;
    //        foreach (Tuple<Key, Value> entry in bucketList)
    //            result += "<" + entry.Item1.ToString() + "," + entry.Item2.ToString() + "> , ";
    //        result += ";";
    //        return result;
    //    }//end ToString


    //}//end class Bucket

    public class LinkedHashTable<Key, Value> : Table<Key, Value> //, ICollection<KeyValuePair<Key,Value>>
    {
        public const int DEFAULT_SIZE = 1;
        public const double DEFAULT_REHASH_THRESHOLD = 0.65;
        public const double RESIZE_AMOUNT = 0.5;

        public Boolean IsReadOnly { get { return false; } private set{} }

        /// <summary>
        /// The number of entries in the hashtable. Read only.
        /// </summary>
        public int Count { get { return this._numberOfEntries; } private set {} }
        public int CurrentSize { get { return this._numberOfBuckets; } private set { } }

        public static readonly string OUTPUT_STRING = "**********************" + System.Environment.NewLine;

        private int _numberOfBuckets = 0;
        private int _numberOfEntries = 0;
        private double _rehashRatioThreshold = 0;
        private int _rehashChainingThreshold = 5;
        private bool _triggerRehash = false;
        private List<KeyValuePair<Key, Value>>[] _theArray;

        /// <summary>
        /// Default constructor. Calls the (int,double) constructor,
        /// passing DEFAULT_SIZE and DEFAULT_REHASH_THRESHOLD
        /// </summary>
        public LinkedHashTable() : this(DEFAULT_SIZE, DEFAULT_REHASH_THRESHOLD) { }

        /// <summary>
        /// Primary constructor.  Takes an integral initial capacity and
        /// a load threshold between 0 and 1 and initializes the hashtable
        /// Throws ArgumentOutOfRangeException
        /// </summary>
        /// <param name="capacity">
        /// an integer specifying the inital number of buckets
        /// if capacity is not positive, an ArgumentOutOfRangeException
        /// will be thrown
        /// </param>
        /// <param name="loadThreshold">
        /// a double specifying the ratio of entries : buckets
        /// at which a rehash/resize will be triggered
        /// if this value is outside of (0,1] then 
        /// an ArgumentOutOfRangeException will be thrown
        /// </param>
        public LinkedHashTable(int capacity, double loadThreshold)
        {
            if ((loadThreshold > 0) && (loadThreshold <= 1))
                _rehashRatioThreshold = loadThreshold;
            else
                throw new ArgumentOutOfRangeException("loadThreshold", "loadThreshold must fall in (0,1]");
            
            if (capacity > 0)
                _numberOfBuckets = capacity;
            else
                throw new ArgumentOutOfRangeException("capacity", "capacity must be positive");

            _theArray = new List<KeyValuePair<Key, Value>>[_numberOfBuckets];
            for (int i = 0; i < _numberOfBuckets; i++)
                _theArray[i] = new List<KeyValuePair<Key, Value>>();
        }//end LinkedHashTable

        /// <summary>
        /// Add a new entry in the hash table. If an entry with the
        /// given key already exists, it is replaced without error.
        /// put() always succeeds.
        /// </summary>
        /// <param name="k">the key for the new or existing entry</param>
        /// <param name="v">the (new) value for the key</param>
        public void Put(Key k, Value v)
        {
            //first check if we need to rehash
            if (_triggerRehash == true)
                rehash();

            //figure out the hash value of the key
            uint keyHash = hashKey(k);

            //check if this is a duplicate key
            int duplicateIndex = _theArray[keyHash].FindIndex((KeyValuePair<Key,Value> t) => t.Key.Equals(k));
            
            if (duplicateIndex != -1)
                //this is a duplicate key, so we need to replace the old entry with the new one
                _theArray[keyHash][duplicateIndex] = new KeyValuePair<Key,Value>(k,v);
            else
            {
                //this is not a duplicate, just add the new entry and increment numberOfEntries
                _theArray[keyHash].Add(new KeyValuePair<Key,Value>(k,v));
                _numberOfEntries++;
            }//end else


            if (((double)_numberOfEntries / _numberOfBuckets >= _rehashRatioThreshold) || _theArray[keyHash].Count > _rehashChainingThreshold)
                _triggerRehash = true;

        }//end Put(Key,Value)

        /// <summary>
        /// Add a new entry in the hash table. If an entry with the
        /// given key already exists, it is replaced without error.
        /// Add() always succeeds.
        /// </summary>
        /// <param name="keyValuePair">the KeyValuePair representing the new entry</param>
        public void Add(KeyValuePair<Key, Value> keyValuePair)
        {
            this.Put(keyValuePair.Key, keyValuePair.Value);
            return;
        }//end Put(tuple)

        /// <summary>
        /// Remove an entry from the hashtable.
        /// </summary>
        /// <param name="item">
        /// an entry to remove from the hashtable
        /// this method only matches the Key of item, the Value does not have to match for the entry to be removed
        /// </param>
        /// <returns>TRUE if a matching entry was removed, otherwise FALSE</returns>
        public bool Remove(KeyValuePair<Key, Value> item)
        {
            //figure out the hash value of the key
            uint keyHash = hashKey(item.Key);

            //check if we have that key
            //int keyIndex = theArray[keyHash].FindIndex((Tuple<Key, Value> t) => t.Item1.Equals(k));
            foreach (var entry in _theArray[keyHash])
                if (entry.Key.Equals(item.Key))
                {
                    _theArray[keyHash].Remove(entry);
                    return true;
                }//end if
            //throw new NonExistentKey<Key>(k);
            return false;
        }//end Remove


        /// <summary>
        /// Does an entry with the given key exist?
        /// </summary>
        /// <param name="k">the key being sought</param>
        /// <returns>true iff the key exists in the table</returns>
        public bool Contains(Key k)
        {

            //figure out the hash value of the key
            uint keyHash = hashKey(k);

            //check if we have that key
            //int keyIndex = theArray[keyHash].FindIndex((Tuple<Key, Value> t) => t.Item1.Equals(k));
            foreach (var entry in _theArray[keyHash])
                if (entry.Key.Equals(k))
                    return true;
            return false;
            
            //we have it if keyIndex != -1
            //return (keyIndex != -1);

        }//end Contains(Key)

        /// <summary>
        /// Does an entry with a matching key exist?
        /// </summary>
        /// <param name="item">an entry whose key is to be matched</param>
        /// <returns>true if a matching key was found, otherwise false</returns>
        public bool Contains(KeyValuePair<Key, Value> item)
        {
            return (this.Contains(item.Key));
        }//end Contains

        /// <summary>
        /// Fetch the value associated with the given key.
        /// </summary>
        /// <param name="k">The key to be looked up in the table</param>
        /// <returns>the value associated with the given key</returns>
        /// <exception cref="NonExistentKey">if Contains(key) is false</exception>
        public Value Get(Key k)
        {
            //figure out the hash value of the key
            uint keyHash = hashKey(k);

            //check if we have that key
            //int keyIndex = theArray[keyHash].FindIndex((Tuple<Key, Value> t) => t.Item1.Equals(k));
            foreach (var entry in _theArray[keyHash])
                if (entry.Key.Equals(k))
                    return entry.Value;
            throw new NonExistentKey<Key>(k);


        }//end Get(Key)

        private uint hashKey(Key k)
        {
            uint hash = (uint)k.GetHashCode();
            hash %= (uint)_numberOfBuckets;
            return hash;
        }//end HashKey

        private void rehash()
        {
            //first turn off the rehash flag
            _triggerRehash = false;

            //now get all our entries back
            List<KeyValuePair<Key, Value>> allEntries = new List<KeyValuePair<Key, Value>>();
            foreach (var list in _theArray)
                allEntries.AddRange(list);
            _numberOfEntries = 0;

            //resize and reset the array
            Array.Clear(_theArray, 0, _numberOfBuckets);
            _numberOfBuckets += (int)Math.Ceiling(_numberOfBuckets * RESIZE_AMOUNT);
            Array.Resize<List<KeyValuePair<Key, Value>>>(ref _theArray, _numberOfBuckets);
            for (int i = 0; i < _numberOfBuckets; i++)
                _theArray[i] = new List<KeyValuePair<Key, Value>>();

            //add the entries back in
            foreach (KeyValuePair<Key, Value> entry in allEntries)
                Add(entry);
        }//end Rehash

        //public IEnumerator<KeyValuePair<Key, Value>> GetEnumerator()
        //{
        //    foreach (var list in theArray)
        //        foreach (var entry in list)
        //            yield return entry;

        //}//end GetEnumerator

        /// <summary>
        /// Generic enumerator for the keys in the hashtable
        /// </summary>
        /// <returns></returns>
        public IEnumerator<Key> GetEnumerator()
        {
            foreach (var list in _theArray)
                foreach (var entry in list)
                    yield return entry.Key;
        }//end GetEnumerator

        /// <summary>
        /// Non-generic enumerator for the keys in the hashtable
        /// </summary>
        /// <returns></returns>
        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return (this.GetEnumerator());
        }//end IEnumerable.GetEnumerator()

        /// <summary>
        /// ToString method displaying either just the entries, or the internal structure of the table
        /// </summary>
        /// <param name="debug">
        /// If true, the internal array will be represented in the string
        /// If false or not specified, just the entries will be represented in the string
        /// </param>
        /// <returns>A string representing the hashtable</returns>
        public String ToString(bool debug = false)
        {
            string result = OUTPUT_STRING;
            if (debug)
                for (int i = 0; i < _numberOfBuckets; i++)
                    result += "* " + i.ToString("00") + ": " + BucketToString(_theArray[i]) + System.Environment.NewLine;
            else
                foreach (Key k in this)
                    result += "* " + k + " -> " + Get(k) + System.Environment.NewLine;

            result += OUTPUT_STRING;
            return result;

        }//end ToString

        /// <summary>
        /// Writes ToString(debug=true) to standard output
        /// </summary>
        public void Debug(string debugLabel = "")
        {
            if (!debugLabel.Equals(""))
                Console.WriteLine(debugLabel);
            Console.Write(this.ToString(true));
            return;
        }//end Debug

        /// <summary>
        /// Copies the hashtable to a KeyValuePair array
        /// Throws ArgumentNullException if the array is null
        /// Throws ArgumentOutOfRangeException if arrayIndex is negative
        /// Throws ArgumentException if there is not enough room in the array for all the entries
        /// </summary>
        /// <param name="array">the KeyValuePair array to copy to</param>
        /// <param name="arrayIndex">The array index in the array to start copying</param>
        public void CopyTo(KeyValuePair<Key, Value>[] array, int arrayIndex)
        {
            if (array == null)
                throw new ArgumentNullException();
            else if (arrayIndex < 0)
                throw new ArgumentOutOfRangeException();
            else if ((array.GetUpperBound(0) - arrayIndex) < _numberOfEntries)
                throw new ArgumentException();

            int i = arrayIndex;
            foreach (var list in _theArray)
                foreach (var pair in list)
                {
                    array[i] = pair;
                    i++;
                }//end foreach

        }//end CopyTo

        /// <summary>
        /// Clears all entries from the hashtable
        /// </summary>
        public void Clear()
        {
            _numberOfEntries = 0;
            _theArray = new List<KeyValuePair<Key, Value>>[_numberOfBuckets];
            for (int i = 0; i < _numberOfBuckets; i++)
                _theArray[i] = new List<KeyValuePair<Key, Value>>();
        }//end Clear

        /// <summary>
        /// Creates a string representation of a "bucket" (a list of KeyValuePair)
        /// </summary>
        /// <param name="bucket">the KeyValuePair list to be represented</param>
        /// <returns>a string representation of the bucket</returns>
        public static String BucketToString(List<KeyValuePair<Key,Value>> bucket)
        {
            string result = string.Empty;
            foreach (var entry in bucket)
                result += "<" + entry.Key.ToString() + "," + entry.Value.ToString() + "> ";
            return result;
        }//end ToString


    }//end class LinkedHashTable

    namespace Test
    {

        using System.Diagnostics;
        using System.Collections;

        public class TestTable
        {
            private struct keyStruct
            {
                public int KeyCode;
                public string KeyName;

                public keyStruct(int code, string name)
                {
                    KeyCode = code;
                    KeyName = name;
                }//end keyStruct (int,string)

                public override string ToString()
                {
                    return KeyCode.ToString() + ": " + KeyName;
                }
            }//end struct keyStruct

            private class keyClass
            {
                public int KeyCode { get; set; }
                public string KeyName { get; set; }

                public keyClass(int code, string name)
                {
                    KeyCode = code;
                    KeyName = name;
                }//end keyStruct (int,string)

                public override int GetHashCode()
                {
                    return KeyCode.GetHashCode() ^ KeyName.GetHashCode();
                }

                public override bool Equals(object obj)
                {
                    if (obj is keyClass)
                        if ((obj as keyClass).KeyName == this.KeyName)
                            if ((obj as keyClass).KeyCode == this.KeyCode)
                                return true;
                    return false;
                }

                public override string ToString()
                {
                    return KeyCode.ToString() + ": " + KeyName;
                }
            }//end class keyClass

            public static void test()
            {
                //test the LinkedHashTable class
                //test the default constructor
                LinkedHashTable<string, string> defaultTable = new LinkedHashTable<string, string>();
                Debug.Assert(defaultTable.Count == 0);
                Debug.Assert(defaultTable.CurrentSize == LinkedHashTable<string,string>.DEFAULT_SIZE);
                //defaultTable.Debug();
                //test the (int, double) constructor
                LinkedHashTable<string, string> smallTable = new LinkedHashTable<string, string>(5, 0.65);
                Debug.Assert(smallTable.Count == 0);
                Debug.Assert(smallTable.CurrentSize == 5);
                //smallTable.Debug();

                //test with different key types

                //using a struct
                LinkedHashTable<keyStruct, string> structTable = new LinkedHashTable<keyStruct, string>();
                keyStruct[] keys = new keyStruct[4];
                for (int i = 0; i < 4; i++)
                {
                    keys[i].KeyCode = i;
                    keys[i].KeyName = "Key " + i.ToString();
                }//end for
                for (int i = 0; i < 4; i++)
                {
                    structTable.Put(keys[i], "Value " + i.ToString());
                    //structTable.Debug();
                }//end for
                //test key lookup
                for (int i = 0; i < 4; i++)
                {
                    Debug.Assert(structTable.Contains(new keyStruct(i, "Key " + i.ToString())));
                }//end for
                //add a keyStruct with the same KeyCode and KeyName, with a different value
                structTable.Put(new keyStruct(0,"Key 0"), "New Value 0");
                //structTable.Debug();
                Debug.Assert(structTable.Get(new keyStruct(0, "Key 0")).Equals("New Value 0"));

                //change a keyStruct that's already been added
                keys[1].KeyName += " modified";
                Debug.Assert(!structTable.Contains(keys[1]));

                //test with a custom key class
                LinkedHashTable<keyClass, string> classTable = new LinkedHashTable<keyClass, string>();
                keyClass[] keys1 = new keyClass[4];
                for (int i = 0; i < 4; i++)
                {
                    keys1[i] = new keyClass(i, "Key " + i.ToString());
                }//end for
                for (int i = 0; i < 4; i++)
                {
                    classTable.Put(keys1[i], "Value " + i.ToString());
                    //classTable.Debug();
                }//end for
                //test key lookup
                for (int i = 0; i < 4; i++)
                {
                    //classTable.Debug("classTable Debug");
                    //the following assertion fails if GetHashCode and Equals are not both overridden in the class
                    var testKey = new keyClass(i, "Key " + i.ToString());
                    Debug.Assert(classTable.Contains(testKey), testKey.ToString());
                }//end for
                //add a keyClass with the same KeyCode and KeyName, with a different value
                classTable.Put(new keyClass(0, "Key 0"), "New Value 0");
                //classTable.Debug();
                Debug.Assert(classTable.Get(new keyClass(0, "Key 0")).Equals("New Value 0"));

                //change a keyClass that's already been added
                keys1[1].KeyName += " modified";
                Debug.Assert(!classTable.Contains(keys1[1]));


                //basic tests
                //test Put(Key, Value), as well as rehashing and resizing
                int sum = 0;
                for (int i = 1; i <= 10; i++)
                {
                    sum += i;
                    defaultTable.Put(i.ToString(), sum.ToString());
                    Debug.Assert(defaultTable.Contains(i.ToString()));
                    Debug.Assert(defaultTable.Count == i);
                    //defaultTable.Debug();
                    //Console.Write(defaultTable.ToString());
                    //Console.WriteLine(">" + i + ":" + i.GetHashCode());
                }//end for i
                //test Contains(Key)
                //defaultTable.Debug();
                foreach (string key in defaultTable)
                    Debug.Assert(defaultTable.Contains(key));
                for (char ch = 'a'; ch <= 'z'; ch++)
                {
                    Debug.Assert(!defaultTable.Contains(ch.ToString()));
                    try
                    {
                        defaultTable.Get(ch.ToString());
                    }
                    catch (NonExistentKey<string> nek)
                    {
                        Debug.Assert(nek.BadKey.Equals(ch.ToString()));
                    }
                }

                //test Get(Key) and GetEnumerator (via foreach)
                foreach (string key in defaultTable)
                {
                    Debug.Assert(defaultTable.Contains(key));
                    string value = Convert.ToString((Convert.ToInt32(key) * (Convert.ToInt32(key) + 1)) / 2);
                    Debug.Assert(defaultTable.Get(key).Equals(value));
                }//end foreach key

                //performance tests
                int tableSize = 100000;
                Stopwatch sw;
                TimeSpan[,] testResults = new TimeSpan[2,3];

                sw = Stopwatch.StartNew();
                LinkedHashTable<string,int> myHt = new LinkedHashTable<string,int>();
                for (int i = 0; i < tableSize; i++)
                    myHt.Put(i.ToString("00000"),i);
                sw.Stop();
                testResults[0,0] = sw.Elapsed;
                
                sw = Stopwatch.StartNew();
                var dictionary = new Dictionary<string, int>();
                for (int i = 0; i < tableSize; i++)
                    dictionary.Add(i.ToString("00000"), i);
                sw.Stop();
                testResults[0,1] = sw.Elapsed;
                
                sw = Stopwatch.StartNew();
                var hashtable = new Hashtable();
                for (int i = 0; i < tableSize; i++)
                    hashtable.Add(i.ToString("00000"), i);
                sw.Stop();
                testResults[0,2] = sw.Elapsed;

                int temp;
                var testingRuns = 1000000;

                sw = Stopwatch.StartNew();
                for (var i = 0; i < testingRuns; i++)
                {
                    if (myHt.Contains("09999"))
                        temp = myHt.Get("09999");
                    if (myHt.Contains("30000"))
                        temp = myHt.Get("30000");
                }//end for i
                sw.Stop();
                testResults[1,0] = sw.Elapsed;

                sw = Stopwatch.StartNew();
                for (var i = 0; i < testingRuns; i++)
                {
                    if (dictionary.ContainsKey("09999"))
                        temp = dictionary["09999"];
                    if (dictionary.ContainsKey("30000"))
                        temp = dictionary["09999"];

                }//end for i
                sw.Stop();
                testResults[1,1] = sw.Elapsed;

                sw = Stopwatch.StartNew();
                for (var i = 0; i < testingRuns; i++)
                {
                    if (hashtable.ContainsKey("09999"))
                        temp = (int)hashtable["09999"];
                    if (hashtable.ContainsKey("30000"))
                        temp = (int)hashtable["09999"];

                }//end for i
                sw.Stop();
                testResults[1,2] = sw.Elapsed;

                Console.WriteLine("CONSTRUCTION AND POPULATION TESTING RESULTS");
                Console.WriteLine("My hash table: {0}; {1:F2}x", testResults[0, 0], (1.0 * testResults[0, 0].Ticks) / testResults[0, 0].Ticks);
                Console.WriteLine("Dictionary:    {0}; {1:F2}x", testResults[0, 1], (1.0 * testResults[0, 0].Ticks) / testResults[0, 1].Ticks);
                Console.WriteLine("HashTable:     {0}; {1:F2}x", testResults[0, 2], (1.0 * testResults[0, 0].Ticks) / testResults[0, 2].Ticks);
                Console.WriteLine("===========================================");
                Console.WriteLine("SEARCH AND RETRIEVE TESTING RESULTS");
                Console.WriteLine("My hash table: {0}; {1:F2}x", testResults[1, 0], (1.0 * testResults[1, 0].Ticks) / testResults[1, 0].Ticks);
                Console.WriteLine("Dictionary:    {0}; {1:F2}x", testResults[1, 1], (1.0 * testResults[1, 0].Ticks) / testResults[1, 1].Ticks);
                Console.WriteLine("HashTable:     {0}; {1:F2}x", testResults[1, 2], (1.0 * testResults[1, 0].Ticks) / testResults[1, 2].Ticks);
                Console.WriteLine("===========================================");
                //Console.WriteLine("My hash table took {0:F2}% as long as the dictionary and {1:F2}% as long as the HashTable, with table size = {2}", (100.0 * myHtTest.Ticks) / dictTest.Ticks, (100.0 * myHtTest.Ticks) / htTest.Ticks, tableSize);



            }//end test

        }//end class TestTable
    }//end namespace Tharp_Test

}//end namespace Tharp